<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>js 手写题</title>
</head>

<body>
    <script>
        /**
         * 一：call 实现
         * 1:第一个参数为null或是undefined时，this指向全局对象window值为原始值的指向该原始值的自动包装对象，如String、Number、Bollean
         * 2:为了避免函数名与上下文(context)的属性发生冲突,使用Symbol类型作为唯一值
         * 3:将函数作为传入的上下文(context)属性执行
         * 4:函数执行完后删除该属性
         * 5:返回该执行结果 
         */
        Function.prototype.myCall = function (context, ...args) {
            let ctx = context || window;
            // 将当前被调用的方法定义在ctx.func上,(为了能以对象调用形式绑定this)
            // 新建一个唯一的Symbol变量避免重复
            let func = Symbol();
            ctx[func] = this;
            args = args ? args : [];
            // 以对象调用形式调用func,此时this指向ctx，也就是传入的需要绑定的this指向
            const res = args.length > 0 ? ctx[func](...args) : ctx[func]();

            // 删除该方法，不然会对传入对象造成污染
            delete ctx[func];
            return res;
        }

        /**
         * 二：apply 实现
         * 1:前部分与call一样
         * 2:第二个参数可以不传，但类型必须为数组或者类数组
         */
        Function.prototype.myApply = function (context, args = []) {
            let ctx = context || window;
            // 将当前被调用的方法定义在ctx.func上(为了能以对象的形式绑定this)
            // 新建一个唯一的Symbol变量避免重复
            let func = Symbol();
            ctx[func] = this;
            // 以对象调用形式调用func,此时this指向ctx 也就是传入的需要绑定的this指向
            const res = args.length > 0 ? ctx[func](...args) : ctx[func]();
            delete ctx[func];
            return res;
        }

        /**
         * 三： bind 的实现
         * bind()除了this外，还可传入多个参数
         * bind创建的新函数可能传入多个参数
         * 新函数可能被当作构造函数调用
         * 函数可能有返回值
         * bind方法不会立即执行，需要返回一个待执行的函数（闭包）
         * 参数传递(apply的数组传参)
         * 当作为构造函数的时候，进行原型继承
         */
        Function.prototype.myBind = function (context, ...args) {
            // 新建一个变量赋值为this,标识当前函数
            const fn = this;
            // 判断有没有传参进来，若为空则赋值[]
            args = args ? args : [];
            // 返回一个newFn函数，在里面调用fn
            return function newFn(...newFnArgs) {
                if (this instanceof newFn) {
                    return new fn(...args, ...newFnArgs);
                }
                return fn.apply(context, [...args, ...newFnArgs]);
            }
        }


        // call、apply、bind测试
        let nam = '乔丹',
            team = '公牛';
        let obj = {
            name: '科比',
            team: this.team,
            myFun: function (year) {
                console.log(this.name + ':所在球队' + this.team + ',共效力' + year + '年');
            }
        };
        let db = {
            name: '詹姆斯',
            team: '骑士'
        };

        obj.myFun.myCall(db, 4);
        obj.myFun.myApply(db, [4]);
        obj.myFun.myBind(db, 4)();
        obj.myFun.myBind(db, [4])();


        /**
         *  四：寄生式组合继承
         */
        function Persion(obj) {
            this.name = obj.name;
            this.age = obj.age;
        }

        Persion.prototype.add = function (value) {
            console.log(value);
        }

        const p1 = new Persion({
            name: '小明',
            age: 18
        });

        console.log(p1);

        function Persion1(obj) {
            Persion.call(this, obj);
            this.sex = obj.sex;
        }

        // 这一步是继承的关键
        Persion1.prototype = Object.create(Persion.prototype);
        Persion1.prototype.constructor = Persion1;

        Persion1.prototype.play = function (value) {
            console.log(value);
        }

        const p2 = new Persion1({
            name: '小王',
            age: 20,
            sex: '男'
        });
        console.log(p2);


        /**
         * Object.create()
         */
        let obj1 = {
            name: '小明',
            age: 18,
            introduce: function () {
                console.log('姓名：' + this.name + ',年龄：' + this.age)
            }
        }

        let obj2 = Object.create(obj1);
        obj2.name = '小王';
        obj2.age = 20;
        obj2.introduce();
        console.log(obj1);


        /**
         * 五： ES6继承
         * class 相当于es5中构造函数
         * class中定义方法时，前后不能加function，全部定义在class的prototype属性中
         * class中定义的所有方法是不可枚举的
         * class中只能定义方法，不能定义对象，变量等
         * class和方法内默认都是严格模式
         * es5中constructor为隐士属性
         */
        class People {
            constructor(name, age) {
                this.name = name;
                this.age = age;
            }
            eat() {
                console.log(`${this.name} ${this.age} eat food!`);
            }
        }

        // 继承父类
        class Woman extends People {
            constructor(name, age) {
                // 继承父类的属性和方法
                super(name, age);
            }
        }

        let people = new Woman('笑笑', 18);
        people.eat();


        /**
         * 六：new 的实现
         */
        function Ctor() {
            // .....
        }

        function myNew(ctor, ...args) {
            if (typeof ctor !== 'function') {
                throw 'myNew function the first param must be a function';
            }
            const newObj = Object.create(ctor.prototype); // 创建一个继承自ctor.prototype的新对象
            const ctorReturnResult = ctor.apply(newObj, args); // 将构造函数ctor的this绑定到newObj中
            const isObject = typeof ctorReturnResult === 'object' && ctorReturnResult !== null;
            const isFunction = typeof ctorReturnResult === 'function';
            if (isObject || isFunction) {
                return ctorReturnResult;
            }
            return newObj;
        }

        let c = myNew(Ctor);

        /**
         * 七：instanceof的实现
         * 1：instanceof 是用来判断A是否为B的实例，表达式为：A instanceof B，如果A是B的实例，则返回true，否则返回false
         * 2：instanceof运算符用来测试一个对象在其原型链中是否存在一个构造函数的prototype属性。
         * 3：不能检测基本数据类型，在原型链上的结果未必准确，不饿能检测null,undefined
         * 4: 实现，遍历左边变量的原型链，知道找到右边变量的prototype，如果没有找到，返回false
         */
        function myInstanceOf(a, b) {
            let left = a.__proto__;
            let right = b.prototype;
            while (true) {
                if (left == null) {
                    return false;
                }

                if (left == right) {
                    return true;
                }

                left = left.__proto__;
            }
        }

        // 测试
        function A() {

        }
        const a = new A();
        console.log(a instanceof A);


        /**
         * 八：Object.create()实现
         * Object.create()会将参数对象作为一个
         */

        function myCreate(obj) {
            // 新声明一个函数
            function C() {};
            // 将函数的原型指向obj
            C.prototype = obj;
            // 返回这个函数的实例化对象
            return new C();
        }

        // 测试
        let obj3 = {
            name: '张三',
            age: '18',
            introduce() {
                console.log(`my name is ${this.name}, ${this.age} years old!`);
            }
        };

        let newObj3 = myCreate(obj3);
        newObj3.name = '李四';
        newObj3.age = 20;
        newObj3.introduce();
        obj3.introduce();
        console.log(obj3, newObj3);


        /**
         * 九：Object.assign()
         * 1: Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象分配到目标对象，它将返回目标对象
         */

        Object.myAssign = function (target, ...source) {
            if (target == null) {
                throw new TypeError('Cannot convert undefined or null to object');
            }

            let ret = Object(target);
            source.forEach(obj => {
                if (obj != null) {
                    for (let key in obj) {
                        if (obj.hasOwnProperty(key)) {
                            ret[key] = obj[key];
                        }
                    }
                }
            });
            return ret;
        }

        // 测试
        let target = {
            a: '11',
            b: '22'
        };
        let source = {
            b: '33',
            c: '44'
        };
        console.log(Object.assign(target, source));


        /**
         * Promise 实现
         * 1：Promise本质是一个状态机，且状态只能为以下三种：Pending(等待态)、
         * Fulfilled(执行态)、Rejected(拒绝态),状态的变更是单向的，只能从Pending->
         * Fulfilled或Pending->Rejected,状态变更不可逆
         * 2：then需要支持链式调用
         */ 
        class Promise {
            callbacks = [];
            state = 'pending'; // 增加状态
            value = null; // 保存结果

            constructor(fn){
                fn(this._resolve.bind(this), this._reject.bind(this));
            }

            then(onFulfilled, onRejected){
                return new Promise((resolve, reject) => {
                    this._handle({
                        onFulfilled: onFulfilled || null,
                        onRejected: onRejected || null,
                        resolve: resolve,
                        reject: reject
                    });
                });
            }

            _handle(callback) {
                if(this.state === 'pending'){
                    this.callbacks.push(callback);
                    return;
                }

                let cb = this.state === 'fulfilled' ? callback.onFulfilled : callback.onRejected;

                // 如果then中没有传递任何东西
                if(!cb){
                    cb = this.state === 'fulfilled'? callback.resolve : callback.reject;
                    cb(this.value);
                    return;


                }
            }
        }

    </script>
</body>

</html>